#include <stdint.h>
#include <string.h>
#include "hx71x.h"
#include "commonapi.h"
#include "fbatt.h"
#include "profile.h"
#include "protocal.h"
#include "psm.h"
#ifdef SOFT_AP
#include "nbiot.h"
#endif
#include "hp303.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "time.h"

#include "esp_log.h"

#define PROTO_HEADER 0x7E
#define PROTO_TAIL 0x7E
#define PROTO_VER 0x01

#define LOGIN_STATUS_OFFLINE 0
#define LOGIN_STATUS_LOGING 1
#define LOGIN_STATUS_REQUEST 2
#define LOGIN_STATUS_REQUESTING 3
#define LOGIN_STATUS_ONLINE 4
#define LOGIN_STATUS_LOGOUT 5
#define LOGIN_STATUS_WAITING_SLEEP 6

#define DATA_BUF_CNT 20
#define REALTIME_BUF_CNT 60
#define DATA_MAX_LEN 64

typedef struct
{
    uint8_t flag;
    uint8_t ver;
    uint8_t device_id[12];
    uint8_t role;
    uint8_t msg_id;
    uint8_t attribute;
    uint8_t seq[2];
    uint8_t len;
} protocal_header_t;

static const char* TAG = "protocal";

// static TickType_t heartbeat_timer;
static TickType_t info_upload_timer;

static msg_t data_queue[DATA_BUF_CNT];
static msg_t realtime_data_queue[REALTIME_BUF_CNT];
static data_queue_t queue_info;
static data_queue_t realtime_queue_info;

static uint8_t pre_shortout_status = 0xff;
// static uint8_t short_debounce;
// static uint8_t press_debounce;
static uint16_t msg_seq;
static uint8_t login_status;
static uint8_t pre_runnint_mode;
static uint16_t pre_press_value;
#ifdef SOFT_AP
static bool csq_update;
#endif
static uint8_t key_detect_debounce;

static location_t location_info;
static SemaphoreHandle_t xSemaphoreHandle_gps;
static SemaphoreHandle_t xSemaphoreHandle_queue;

extern uint8_t fbatt_get_percent(void);
extern uint32_t HX717_Read(uint8_t channel);

static void queue_add(msg_t* pmsg, bool is_realtime)
{
    if (xSemaphoreHandle_queue == NULL)
    {
        return;
    }
    if (xSemaphoreTake(xSemaphoreHandle_queue, 1000 / portTICK_PERIOD_MS) == pdTRUE)
    {
        if (is_realtime)
        {
            memcpy(&realtime_data_queue[realtime_queue_info.tail++], pmsg, sizeof(msg_t));
            if (realtime_queue_info.tail == REALTIME_BUF_CNT)
            {
                realtime_queue_info.tail = 0;
            }
            if (realtime_queue_info.cnt < REALTIME_BUF_CNT)
            {
                realtime_queue_info.cnt++;
            }
        }
        else
        {
            memcpy(&data_queue[queue_info.tail++], pmsg, sizeof(msg_t));
            if (queue_info.tail == DATA_BUF_CNT)
            {
                queue_info.tail = 0;
            }
            if (queue_info.cnt < DATA_BUF_CNT)
            {
                queue_info.cnt++;
            }
        }
        xSemaphoreGive(xSemaphoreHandle_queue);
    }
}

uint16_t protocal_packet(uint8_t* device_id, uint8_t msg_id, uint8_t* data, uint8_t len, uint8_t* input)
{

    uint8_t i = 0;
    uint8_t bcc = 0;
    data[0] = PROTO_HEADER;
    data[1] = PROTO_VER;

    struct tm* ptime;
    time_t rawtime;
    time(&rawtime);
    ptime = localtime(&rawtime);
    if (ptime != 0)
    {
        data[16] = ptime->tm_hour;
        data[17] = ptime->tm_min;
        data[18] = ptime->tm_sec;
    }
    memcpy(&data[2], device_id, 12);

    data[13] |= parity_verify(device_id, 12);
    data[14] = 0x03;
    data[15] = msg_id;
    data[19] = len;
    if (len != 0)
    {
        memcpy(&data[20], input, len);
    }
    while (i < len)
    {
        bcc = bcc ^ data[20 + i];
        i++;
    }
    data[20 + len] = bcc;
    data[21 + len] = PROTO_TAIL;
    msg_seq++;
    return 22 + len;
}

static void data_info_collect(void)
{

    msg_t msg = { 0 };
    uint8_t send_data[16] = "";
    uint8_t data_len = 0;
    uint32_t hx717_value = 0;
    uint16_t press_value = 0;
    uint8_t current_status = 0;
#ifdef SOFT_AP
    if (!csq_update)
    {
        uint8_t csq = nbiot_get_csq();
        if (csq != 0)
        {
            send_data[0] = 0x0A;
            send_data[1] = nbiot_get_csq();
            data_len = 2;
            msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, data_len, send_data);
            queue_add(&msg, false);
            csq_update = true;
        }
    }
#endif
    TickType_t cur_timer = xTaskGetTickCount();
    if (info_upload_timer <= cur_timer)
    {
        info_upload_timer = cur_timer + runtime_profile->Struct.location_upload_inteval * 1000 / portTICK_PERIOD_MS;
        send_data[0] = 0x03;
        send_data[1] = fbatt_get_percent();
        data_len = 2;
        msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, data_len, send_data);
        queue_add(&msg, false);

#ifdef SOFT_AP
        if (csq_update)
        {
            send_data[0] = 0x0A;
            send_data[1] = nbiot_get_csq();
            data_len = 2;
            msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, data_len, send_data);
            queue_add(&msg, false);
        }
        if (xSemaphoreHandle_gps)
        {
            if (xSemaphoreTake(xSemaphoreHandle_gps, 10 / portTICK_PERIOD_MS) == pdTRUE)
            {
                send_data[0] = 0x04;
                if (location_info.fix)
                {
                    //uint16_t mbar = hp303_get_mbar();
                    send_data[1] = 1;
                    send_data[2] = (uint8_t)(((uint32_t)(location_info.longitude * 1000000) >> 24) & 0xFF);
                    send_data[3] = (uint8_t)(((uint32_t)(location_info.longitude * 1000000) >> 16) & 0xFF);
                    send_data[4] = (uint8_t)(((uint32_t)(location_info.longitude * 1000000) >> 8) & 0xFF);
                    send_data[5] = (uint8_t)((uint32_t)(location_info.longitude * 1000000) & 0xFF);
                    send_data[6] = (uint8_t)(((uint32_t)(location_info.latitude * 1000000) >> 24) & 0xFF);
                    send_data[7] = (uint8_t)(((uint32_t)(location_info.latitude * 1000000) >> 16) & 0xFF);
                    send_data[8] = (uint8_t)(((uint32_t)(location_info.latitude * 1000000) >> 8) & 0xFF);
                    send_data[9] = (uint8_t)((uint32_t)(location_info.latitude * 1000000) & 0xFF);
                    send_data[10] = (uint8_t)(((uint32_t)(location_info.altitude * 10) >> 8) & 0xFF);
                    send_data[11] = (uint8_t)((uint32_t)(location_info.altitude * 10) & 0xFF);
                    send_data[12] = 0;
                    send_data[13] = 0;
                    data_len = 14;
                    msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, data_len, send_data);
                    queue_add(&msg, false);
                }
                xSemaphoreGive(xSemaphoreHandle_gps);
            }
        }
#endif
    }
    if (hx717_get_value(3, &hx717_value))
    {
        if (0x00f00000 >= hx717_value)
        {
            current_status = 1;
        }
        if (current_status != pre_shortout_status)
        {
            if (key_detect_debounce++ == 5)
            {
                key_detect_debounce = 0;
                pre_shortout_status = current_status;
                send_data[0] = 0x06;
                send_data[1] = pre_shortout_status;
                data_len = 2;
                msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, data_len, send_data);
                queue_add(&msg, true);
            }
        }
        else
        {
            key_detect_debounce = 0;
        }
    }

    hx717_value = 0;
    if (hx717_get_value(2, &hx717_value))
    {
        if (hx717_value != 0x00FFFFFF)
        {
            press_value = hx717_value / 640;
            if ((press_value - 500 > pre_press_value) || (press_value + 500 < pre_press_value))
            {
                pre_press_value = press_value;
                send_data[0] = 0x05;
                send_data[1] = press_value >> 8;
                send_data[2] = press_value;
                data_len = 3;
                msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, data_len, send_data);
                queue_add(&msg, true);
            }
        }
    }

    if (pre_runnint_mode != psm_get_status())
    {
        pre_runnint_mode = psm_get_status();
        send_data[0] = 0x08;
        send_data[1] = 0x02;
        data_len = 2;
        msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, data_len, send_data);
        queue_add(&msg, true);
    }
}

static void login_collect(void)
{
    msg_t msg = { 0 };
    msg.len = protocal_packet(runtime_profile->Struct.device_id, 0x01, msg.data, 0, NULL);
    queue_add(&msg, true);
}

static void logout_collect(void)
{

    msg_t msg = { 0 };
    uint8_t send_data[64] = { 0 };
    pre_runnint_mode = psm_get_status();
    send_data[0] = 0x08;
    send_data[1] = pre_runnint_mode;
    msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, 2, send_data);
    queue_add(&msg, true);
    send_data[0] = 0x03;
    send_data[1] = fbatt_get_percent();
    msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, 2, send_data);
    queue_add(&msg, true);

    msg.len = protocal_packet(runtime_profile->Struct.device_id, 0x02, msg.data, 0, NULL);
    queue_add(&msg, true);
    ESP_LOGI(TAG, "log out\n");
}

static void onetime_data_collect(void)
{
    msg_t msg = { 0 };
    uint8_t send_data[16] = "";

    //send sn
#ifdef SOFT_AP
    send_data[0] = 0x09;
    nbiot_get_sn(&send_data[1]);
    msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, 11, send_data);
    queue_add(&msg, true);
#endif
    send_data[0] = 0x07;
    send_data[1] = 12;
    msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, 2, send_data);
    queue_add(&msg, true);

    send_data[0] = 0x1;
    send_data[1] = runtime_profile->Struct.heartbeat_inteval;
    msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, 2, send_data);
    queue_add(&msg, true);

    send_data[0] = 0x2;
    send_data[1] = runtime_profile->Struct.location_upload_inteval >> 8;
    send_data[2] = runtime_profile->Struct.location_upload_inteval;
    msg.len = protocal_packet(runtime_profile->Struct.device_id, 7, msg.data, 3, send_data);
    queue_add(&msg, true);

    msg.len = protocal_packet(runtime_profile->Struct.device_id, 6, msg.data, 0, NULL);
    queue_add(&msg, true);
}

bool protocal_queue_get_data(msg_t* pmsg)
{
    bool ret = false;
    if (xSemaphoreTake(xSemaphoreHandle_queue, 100 / portTICK_PERIOD_MS) == pdTRUE)
    {
        if (realtime_queue_info.cnt)
        {
            memcpy(pmsg, &realtime_data_queue[realtime_queue_info.head++], sizeof(msg_t));
            if (realtime_queue_info.head == REALTIME_BUF_CNT)
            {
                realtime_queue_info.head = 0;
            }
            realtime_queue_info.cnt--;
            ret = true;
        }
        else if (queue_info.cnt)
        {
            memcpy(pmsg, &data_queue[queue_info.head++], sizeof(msg_t));
            if (queue_info.head == DATA_BUF_CNT)
            {
                queue_info.head = 0;
            }
            queue_info.cnt--;
            ret = true;
        }
        xSemaphoreGive(xSemaphoreHandle_queue);
    }
    return ret;
}

void protocal_collect_schedule(void)
{
    switch (login_status)
    {
    case LOGIN_STATUS_OFFLINE:
        login_collect();
        // login_status = LOGIN_STATUS_LOGING;
        onetime_data_collect();
        login_status = LOGIN_STATUS_ONLINE;
        break;
    case LOGIN_STATUS_ONLINE:
        data_info_collect();
        break;
    case LOGIN_STATUS_LOGOUT:
        logout_collect();
        login_status = LOGIN_STATUS_WAITING_SLEEP;
        break;
    case LOGIN_STATUS_WAITING_SLEEP:
        break;
    default:
        break;
    }
    //send heartbeat
#if 0
    if (queue_info.cnt == 0 && !is_data_sending && LOGIN_STATUS_LOGING < login_status && login_status < LOGIN_STATUS_LOGOUT)
    {
        if (heartbeat_timer == 0)
        {
            heartbeat_timer = xTaskGetTickCount() + runtime_profile->Struct.heartbeat_inteval * (1000 / portTICK_PERIOD_MS);
        }
        else if (xTaskGetTickCount() >= heartbeat_timer)
        {
            heartbeat_timer = xTaskGetTickCount() + runtime_profile->Struct.heartbeat_inteval * (1000 / portTICK_PERIOD_MS);
            msg.len = protocal_packet(runtime_profile->Struct.device_id, 3, msg.data, 0, NULL);
            queue_add(&msg);
        }
    }
#endif
}

void protocal_location_update(location_t* pLocation)
{
    if (pLocation)
    {
        if (xSemaphoreHandle_gps)
        {
            if (xSemaphoreTake(xSemaphoreHandle_gps, 0) == pdTRUE)
            {
                memcpy(&location_info, pLocation, sizeof(location_t));
                xSemaphoreGive(xSemaphoreHandle_gps);
            }
        }
    }
}

void protocal_rx_handle(uint8_t* data, uint8_t len)
{
    switch (data[15])
    {
    case 0x81: //login ack
        //login_status = LOGIN_STATUS_REQUEST;
        break;
    case 0x82: //logout ack
        break;
    case 0x83: //heartbeat ack
        break;
    case 0x86: //req ack
        //login_status = LOGIN_STATUS_ONLINE;
        break;
    case 0x87:
        break;
    default:
        break;
    }
}

#ifdef SOFT_AP
void protocal_server_rx_handle(uint8_t* data, uint8_t len, uint8_t* resp, uint8_t* resplen)
{
    uint8_t bbc = 0;
    msg_t msg = { 0 };
    uint8_t send_data[64] = "";
    uint8_t i;
    uint8_t resp_code = 2;
    uint8_t head_len = sizeof(protocal_header_t);
    protocal_header_t* pHeader = (protocal_header_t*)data;

    if (pHeader->len > 254)
    {
    }
    else if (pHeader->len > 0)
    {
        for (i = 0; i < pHeader->len; i++)
        {
            bbc ^= data[head_len + i];
        }
        if (bbc == data[head_len + pHeader->len + 1])
        {
            resp_code = 1;
        }
    }
    else
    {
        resp_code = 1;
    }
    if (pHeader->msg_id != 0x03)
    {
        bool is_true_data = true;
        switch (data[sizeof(protocal_header_t)])
        {
        case 0x01:
            send_data[0] = 0x09;
            nbiot_get_sn(&send_data[1]);
            msg.len = protocal_packet(pHeader->device_id, 7, msg.data, 11, send_data);
            queue_add(&msg, true);
            break;
        case 0x03:
            if (csq_update)
            {
                uint8_t csq = nbiot_get_csq();
                if (csq != 0)
                {
                    send_data[0] = 0x0A;
                    send_data[1] = nbiot_get_csq();
                    msg.len = protocal_packet(pHeader->device_id, 7, msg.data, 2, send_data);
                    queue_add(&msg, false);
                }
            }
            break;
        case 0x04:
            if (xSemaphoreHandle_gps)
            {
                if (xSemaphoreTake(xSemaphoreHandle_gps, 10 / portTICK_PERIOD_MS) == pdTRUE)
                {
                    double ap_hight = 0, station_hight = 0;
                    float relative_hight = 0;
                    int16_t relative_hight10 = 0;
                    memcpy(&station_hight, &data[sizeof(protocal_header_t) + 1], 8);
                    ap_hight = hp303_get_hight_value();
                    relative_hight = station_hight - ap_hight;

                    relative_hight10 = (int16_t)(relative_hight * 100);
                    ESP_LOGI(TAG, "ap: %f, station: %f, relative: %f, %hd\n", ap_hight, station_hight, relative_hight, relative_hight10);
                    send_data[0] = 0x04;
                    if (location_info.fix)
                    {
                        send_data[1] = 1;
                        send_data[2] = (uint8_t)(((uint32_t)(location_info.longitude * 1000000) >> 24) & 0xFF);
                        send_data[3] = (uint8_t)(((uint32_t)(location_info.longitude * 1000000) >> 16) & 0xFF);
                        send_data[4] = (uint8_t)(((uint32_t)(location_info.longitude * 1000000) >> 8) & 0xFF);
                        send_data[5] = (uint8_t)((uint32_t)(location_info.longitude * 1000000) & 0xFF);
                        send_data[6] = (uint8_t)(((uint32_t)(location_info.latitude * 1000000) >> 24) & 0xFF);
                        send_data[7] = (uint8_t)(((uint32_t)(location_info.latitude * 1000000) >> 16) & 0xFF);
                        send_data[8] = (uint8_t)(((uint32_t)(location_info.latitude * 1000000) >> 8) & 0xFF);
                        send_data[9] = (uint8_t)((uint32_t)(location_info.latitude * 1000000) & 0xFF);
                        send_data[10] = (uint8_t)(((uint32_t)(location_info.altitude * 10) >> 8) & 0xFF);
                        send_data[11] = (uint8_t)((uint32_t)(location_info.altitude * 10) & 0xFF);
                        send_data[12] = relative_hight10 >> 8;
                        send_data[13] = relative_hight10;
                        msg.len = protocal_packet(pHeader->device_id, 7, msg.data, 14, send_data);
                        queue_add(&msg, false);
                    }
                    xSemaphoreGive(xSemaphoreHandle_gps);
                }
            }
            is_true_data = false;
            break;
        default:
            break;
        }
        if (data[sizeof(protocal_header_t)] != 0x04)
        {
            msg.len = protocal_packet(pHeader->device_id, pHeader->msg_id, msg.data, pHeader->len, &data[sizeof(protocal_header_t)]);
            queue_add(&msg, is_true_data);
        }
    }
    memcpy(resp, data, sizeof(protocal_header_t));
    pHeader = (protocal_header_t*)resp;
    pHeader->role = 0x03;
    pHeader->len = 1;
    pHeader->msg_id = pHeader->msg_id | 0x80;
    resp[head_len] = resp_code;
    resp[head_len + 1] = 1;
    resp[head_len + 2] = pHeader->flag;
    *resplen = head_len + 3;
}
#endif

void protocal_init(void)
{
    xSemaphoreHandle_gps = xSemaphoreCreateMutex();
    if (xSemaphoreHandle_gps == 0)
    {
        ESP_LOGE(TAG, "create gps sem failed\n");
        while (1)
            ;
    }

    xSemaphoreHandle_queue = xSemaphoreCreateMutex();
    if (xSemaphoreHandle_queue == 0)
    {
        ESP_LOGE(TAG, "create queue sem failed\n");
        while (1)
            ;
    }
}

void protocal_goto_sleep(void)
{
    if (login_status != LOGIN_STATUS_LOGOUT)
    {
        login_status = LOGIN_STATUS_LOGOUT;
    }
}

uint8_t protocal_get_status(void)
{
    return login_status;
}

bool protocal_is_has_data(void)
{
    if (queue_info.cnt == 0 && realtime_queue_info.cnt == 0)
    {
        return false;
    }
    return true;
}

bool protcal_is_logout(void)
{
    return LOGIN_STATUS_WAITING_SLEEP == login_status;
}